package hxgo

import (
	"reflect"
	"errors"
)

// declare some event caller error
var (
	// caller is not a function
	EventCallerNotFunc = errors.New("invalid function for event caller")
	// caller is not found
	EventCallerNotExist = errors.New("no event caller")
	// caller input params are not matched
	EventCallerInError = errors.New("input data error in event caller")
)

// Event struct
// manage events binding and triggering
type Event struct {
	callbacks map[string]EventCall
}

// EventCall struct
// saving event caller and input/output args
type EventCall struct {
	caller reflect.Value
	numIn  int
	numOut int
}

// On event
// add a function in event
func (this *Event) On(event string, call interface {}) error {
	v := reflect.ValueOf(call)
	if v.Type().Kind().String() != "func" {
		return EventCallerNotFunc
	}
	c := EventCall{}
	c.caller = v
	c.numIn = v.Type().NumIn()
	c.numOut = v.Type().NumOut()
	this.callbacks[event] = c
	return nil
}

// Live event
// add a function in event if no event is added
func (this *Event) Live(event string, call interface {}) error {
	if this.Has(event) {
		return nil
	}
	return this.On(event, call)
}

// Off event
// clear the func binding in event
func (this *Event) Off(event string) {
	this.callbacks[event] = EventCall{}
}

// Check is event binding
// If a function is on event, return true
func (this *Event) Has(event string) bool {
	return this.callbacks[event].caller.IsValid()
}

// Emit event
// Trigger event and return function execution result
// result is []interface as many as returning params
func (this *Event) Emit(event string, args...interface {}) (chan []interface {}, error) {
	caller := this.callbacks[event]
	if !caller.caller.IsValid() {
		return nil, EventCallerNotExist
	}
	if len(args) != caller.numIn {
		return nil, EventCallerInError
	}
	ch := make(chan []interface {})
	go func() {
		argsValue := make([]reflect.Value, caller.numIn)
		for i := 0; i < caller.numIn; {
			argsValue[i] = reflect.ValueOf(args[i])
			i++
		}
		out := caller.caller.Call(argsValue)
		rs := make([]interface {}, len(out))
		if len(out) < 1 {
			ch <- rs
		}
		for i, v := range out {
			rs[i] = v.Interface()
		}
		ch <-rs
	}()
	return ch, nil
}

// create a new event trigger for binding and emitting
func NewEvent() *Event {
	e := &Event{}
	e.callbacks = make(map[string]EventCall)
	return e
}

